/*************************
Author: Viet Hung Nguyen
Purpose: parse the XML log of Chrome for fixed revisions of security bug
Input: 
- list of security bug (security_bug.txt) located in the same folder of the script
- log file located in log/ folder
Output:
- Revision - Bug CSV file: Revision, CommitDate, BugID
- Revision - modified files: Revison CommitDate, Action, Path
*/
program ChromeLog;

include <core.vhs>
include <netutil.vhs>

import core::*;
import core::Pattern::*;
import core::Logging::*;
import core::JobEngine::*;
import netutil::*;

var SecurityBug = map[];

pattern<DataPart> LoadSecurityBug($dataPattern=@'(\d+)', $minOccur=0, $maxOccur=INFINITY)
	SecurityBug.put(this[1], true)

function main() {	
	configureLog('chromelog.log');
	log("Start processing SVN log");
	
	LoadSecurityBug(input = new SequenceStream('security_bug.txt'));
	log("Security bugs loaded...%d bugs", SecurityBug.size());
	
	var revbug = new CsvFile('output/rev-bug.csv');
	var revfile = new CsvFile('output/rev-file.csv');
	var out = new CsvFile('output/bugfile.csv');
	
	revbug.writeln('Revision,CommitDate,Bug');
	revfile.writeln('Revision,CommitDate,Action,Path');
	out.writeln('Bug,FixRevision,FSF');
	
	var outfunc = function(entry) {
		var isSecurityBug = false;
		for(var bug: entry.bugs.split(@'\s*,\s*')) 
			if (SecurityBug.containsKey(bug.trim())){
				println("Revision %s, bug %s", entry.revision, bug);
				revbug.writeCsvLine([entry.revision, entry.date, bug]);
				for(var p: entry.paths)
					out.writeCsvLine([bug, entry.revision, p[1]]);
				isSecurityBug = true;
			}
		if (isSecurityBug)
			for(var p: entry.paths)
				revfile.writeCsvLine([entry.revision, entry.date, p[0], p[1]]);
	}
	var logfile = "log/gc19.0.1085.0.log";
	log("Process log file: %s", logfile);
	SvnLogEntry(input = new SequenceStream(logfile, 4096), outfunc=outfunc);	
	revbug.close();
	revfile.close();
	log("Completed");
}

pattern SvnLogEntry($outfunc, $minOccur=0, $maxOccur=INFINITY) {
	var btag = locate(@'<logentry.+?revision="(\d+)">');
	var revision = btag[1];
	locate_end(@'</logentry>');
	//println("Revision %s, end position = %d, size=%d", revision + '', this.GlobalEndPosition, this.GlobalInput.length());
	var author = ''; //SvnLogEntry_Author();
	var commitDate = SvnLogEntry_CommitDate(includeTime=false);
	var paths = SvnLogEntry_Paths();
	var bugs = SvnLogEntry_Msg();
	
	if (!isMatchFail(bugs))
		outfunc?(struct {revision=revision, author=author, date=commitDate, bugs=bugs, paths=paths} );
}

pattern<DataPart> SvnLogEntry_Author($dataPattern=@'<author>(.+?)</author>') this[1];
pattern<DataPart> SvnLogEntry_CommitDate($dataPattern=@'<date>((\d+-\d+-\d+)T\d+:\d+:\d+)\.\d+Z</date>', $includeTime=false) if (includeTime) this[1] else this[2];
pattern<DataPart> SvnLogEntry_Path($dataPattern=@'<path.+?action="(.)">(.+?)</path>', $minOccur=0, $maxOccur=INFINITY, $listObj) listObj?.add([this[1], this[2]]);
pattern<DataPart> SvnLogEntry_Msg($dataPattern=@'BUG=\s*((http://crbug\.com/)?\d+(\s*,\s*(http://crbug\.com/)?\d+)*)', $minOccur=0) 
					this[1].replaceAll(@'http://crbug\.com/', '');

pattern SvnLogEntry_Paths() {
	locate('<paths>')
	locate_end('</paths>');
	var listObj = list [];
	SvnLogEntry_Path(listObj=listObj);
	return listObj;
}
